﻿
Imports System.Net
Imports System.Net.Sockets
Imports System.Runtime.CompilerServices
Imports std
Imports md5 = safty.md5Calculator

Namespace udpTrans
    Public Module udpTrans
        Private Class _client
            <ThreadStatic()> Public Shared client As UdpClient = Nothing
        End Class

        Private Const rate As Int64 = 10000
        Private Const portStart As Int32 = 5000
        Private ReadOnly UDP_SEND_BYTES_MS As Int64
        Private ReadOnly UDP_RECEIVE_BYTES_MS As Int64
        Private ReadOnly UDP_HASH_ERROR_RATE As Int64
        Private ReadOnly UDP_SEND_ERROR_RATE As Int64
        Private ReadOnly UDP_SUCCESS_ERROR_RATE As Int64
        Private ReadOnly hashlength As Int64 = npos
        Private currentPortLock As sequentialLock = Nothing
        Private currentPort As Int32 = portStart

        Sub New()
            While Not md5.hashsize(hashlength)
            End While
            hashlength >>= bitshiftInByte
            UDP_SEND_BYTES_MS = counter.registerAverageAndLastAverage("UDP_SEND_BYTES_MS")
            UDP_RECEIVE_BYTES_MS = counter.registerAverageAndLastAverage("UDP_RECEIVE_BYTES_MS")
            UDP_HASH_ERROR_RATE =
                counter.registerAverageAndLastAverage("UDP_HASH_ERROR_RATE/" + Convert.ToString(rate))
            UDP_SEND_ERROR_RATE =
                counter.registerAverageAndLastAverage("UDP_SEND_ERROR_RATE/" + Convert.ToString(rate))
            UDP_SUCCESS_ERROR_RATE =
                counter.registerAverageAndLastAverage("UDP_SUCCESS_ERROR_RATE/" + Convert.ToString(rate))
            currentPortLock = New sequentialLock()
            AddHandler applicationShutdown, AddressOf close
        End Sub

        Private Function getAndIncreaseCurrentPort() As Int32
            Dim rtn As Int32 = 0
            currentPortLock.wait()
            rtn = currentPort
            currentPort += 1
            currentPortLock.release()
            Return rtn
        End Function

        Private Function client(Optional ByVal port As Int32 = npos) As UdpClient
            If port <> npos AndAlso Not _client.client Is Nothing Then
                _client.client.Close()
                _client.client = Nothing
            End If
            If _client.client Is Nothing Then
                If port = npos Then
                    port = getAndIncreaseCurrentPort()
                End If
                _client.client = New UdpClient(port)
            End If
            Return _client.client
        End Function

        Public Sub setLocalPort(ByVal port As Int32)
            client(port)
        End Sub

        Private Sub increase(ByVal c As Int64)
            counter.increase(c, 10000)
        End Sub

        Private Sub keep(ByVal c As Int64)
            counter.increase(c, 0)
        End Sub

        Private Function receive(ByRef remoteEndPoint As IPEndPoint, ByRef data() As Byte) As Boolean
            assert(Not remoteEndPoint Is Nothing)
            Try
                data = client().Receive(remoteEndPoint)
            Catch ex As Exception
                raiseError(errorHandle.displayMode.console,
                           "exception caught when receiving, ex " + ex.Message, errorHandle.errorType.system)
                Return False
            End Try
            Return True
        End Function

        Private Function send(ByVal remoteAddress As IPEndPoint, ByVal data() As Byte) As Boolean
            assert(Not remoteAddress Is Nothing)
            assert(Not data Is Nothing)
            Dim sentlen As Int64 = 0
            Try
                sentlen = client().Send(data, data.Length, remoteAddress)
                If sentlen = data.Length Then
                    keep(UDP_SEND_ERROR_RATE)
                Else
                    increase(UDP_SEND_ERROR_RATE)
                End If
                Return sentlen = data.Length
            Catch ex As Exception
                raiseError(errorHandle.displayMode.console,
                           "exception caught when sending, ex " + ex.Message, errorHandle.errorType.system)
                Return False
            End Try
        End Function

        Private Function receiveAndSendSuccess(ByVal remoteAddress As IPEndPoint, ByVal hash() As Byte,
                                               ByVal startTime As Int64, ByVal holdTime As Int64) As Boolean
            assert(Not hash Is Nothing)
            assert(hash.Length() = hashlength)

            While Now.milliseconds() - startTime < holdTime
                Dim response() As Byte = Nothing
                If receive(remoteAddress, response) Then
                    If memcmp(hash, response) = 0 Then
                        Return sendSuccess(remoteAddress, hash, startTime, holdTime)
                    End If
                End If
            End While

            Return False
        End Function

        Private Function sendSuccess(ByVal remoteAddress As IPEndPoint, ByVal hash() As Byte,
                                     ByVal startTime As Int64, ByVal holdTime As Int64) As Boolean
            assert(Not hash Is Nothing)
            assert(hash.Length() = hashlength)

            While Now.milliseconds() - startTime < holdTime
                If send(remoteAddress, hash) Then
                    keep(UDP_SUCCESS_ERROR_RATE)
                    Return True
                End If
            End While
            Return False
        End Function

        Public Sub close()
            client().Close()
            _client.client = Nothing
        End Sub

        Public Function send(ByVal remoteAddress As IPEndPoint,
                             ByVal data As nettransData,
                             ByVal holdTime As Int64) As Boolean
            If remoteAddress Is Nothing Then
                raiseError(errorHandle.displayMode.console, "remoteAddress is nothing.",
                           errorHandle.errorType.exclamation)
                Return False
            End If
            If data Is Nothing Then
                raiseError(errorHandle.displayMode.console, "data is nothing.", errorHandle.errorType.exclamation)
                Return False
            End If

            Dim bt As bytes = Nothing
            If Not data.export(bt) Then
                raiseError(errorHandle.displayMode.console, "cannot export data into bytes.",
                           errorHandle.errorType.exclamation)
                Return False
            End If

            Dim hash() As Byte = Nothing
            If Not md5.signing(bt, hash) Then
                raiseError(errorHandle.displayMode.console, "cannot md5 hash data.", errorHandle.errorType.exclamation)
                Return False
            End If
            If isdebugmode() Then
                assert(hash.Length() = hashlength)
            End If
            bt += hash

            Dim startTime As Int64 = Now.milliseconds()
            While Now.milliseconds() - startTime < holdTime
                If send(remoteAddress, +bt) Then
                    If receiveAndSendSuccess(remoteAddress, hash, startTime, holdTime) Then
                        keep(UDP_SUCCESS_ERROR_RATE)
                        Exit While
                    Else
                        increase(UDP_SUCCESS_ERROR_RATE)
                    End If
                End If
            End While

            Dim costTime As Int64 = Now.milliseconds() - startTime
            If costTime < holdTime Then
                counter.increase(UDP_SEND_BYTES_MS, bt.size() \ costTime)
                Return True
            Else
                Return False
            End If
        End Function

        Public Function receive(ByRef remoteAddress As IPEndPoint, ByRef data As nettransData,
                                ByVal holdTime As Int64,
                                Optional ByVal needOvertimeOrNotConnectedInfo As Boolean = True,
                                Optional ByVal needClearData As Boolean = True) As Boolean
            If remoteAddress Is Nothing Then
                raiseError(errorHandle.displayMode.console, "remoteAddress is nothing.",
                           errorHandle.errorType.exclamation)
                Return False
            End If

            If data Is Nothing Then
                data = New nettransData()
            ElseIf needClearData Then
                data.clear()
            End If

            If remoteAddress Is Nothing Then
                remoteAddress = New IPEndPoint(Net.IPAddress.Any, 0)
            End If
            Dim startTime As Int64 = Now.milliseconds()
            Dim bt() As Byte = Nothing
            While Now.milliseconds() - startTime < holdTime
                If receive(remoteAddress, bt) Then
                    If bt.Length() > hashlength Then
                        Dim hash() As Byte = Nothing
                        If md5.signing(bt, bt.Length() - hashlength, hash) Then
                            If isdebugmode() Then
                                assert(hash.Length() = hashlength)
                            End If
                            If memcmp(bt, hash, bt.Length() - hashlength) = 0 Then
                                keep(UDP_HASH_ERROR_RATE)
                                If sendSuccess(remoteAddress, hash, startTime, holdTime) Then
                                    keep(UDP_SUCCESS_ERROR_RATE)
                                    Exit While
                                Else
                                    increase(UDP_SUCCESS_ERROR_RATE)
                                End If
                            Else
                                increase(UDP_HASH_ERROR_RATE)
                            End If
                        Else
                            raiseError(errorHandle.displayMode.console, "cannot hash data received.",
                                       errorHandle.errorType.exclamation)
                        End If
                    End If
                End If
            End While

            Dim costTime As Int64 = Now.milliseconds() - startTime
            If costTime < holdTime Then
                counter.increase(UDP_RECEIVE_BYTES_MS, bt.Length() \ costTime)
                Return True
            Else
                Return False
            End If
        End Function
    End Module
End Namespace
